
/*
 * Copyright (C) 2010 Bitglue. All rights reserved.
 *
 * $Id: BaseScanner.java bself $
 */

package com.bitglue.osgi.scan;

import com.bitglue.osgi.Plugable;
import com.bitglue.osgi.model.Deployment;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.InitializingBean;

/**
 * BaseScanner implements {@link ScannerInterface} to
 * provide common persistence and parsing scanning
 * functionality to sub class instances.
 * @author Bryan Patrick Self
 */
public abstract class BaseScanner implements ScannerInterface,
  InitializingBean {

    private static final String RPAT = "\\b([\\S]+)-(\\d([\\S.]+))\\.";
    private static final String VPAT = "[.-]([\\D]+)$";

    protected Map<String, Deployment> batch;
    protected Map<String, Deployment> lastBatch;
    protected Plugable plugable;
    protected String scanTarget;
    protected boolean recursive;
    protected String repository;
    protected boolean enabled;
    protected String[] exts;

    private final Logger log = LoggerFactory.getLogger(getClass());
    private Pattern pattern;

    public BaseScanner(String[] exts) {
        this.exts = exts;
        StringBuffer sb = new StringBuffer();
        sb.append("(");
        for (String st : exts) {
            if (sb.length() > 1)
                sb.append("|");
            sb.append(st + "$");
        }
        sb.append(")");
        pattern = Pattern.compile(RPAT + sb);
        lastBatch = new HashMap<String, Deployment>();
        batch = new HashMap<String, Deployment>();
    }

    public void afterPropertiesSet() {
        String pred = "property must be set on " + getClass();
        if (plugable == null)
            throw new RuntimeException("'plugable' " + pred);
        if (scanTarget == null)
            throw new RuntimeException("'scanTarget' " + pred);
        if (repository == null)
            throw new RuntimeException("'repository'" + pred);
    }

    public Deployment parseResource(String resource) {
        Deployment d = new Deployment();
        Matcher matcher = pattern.matcher(resource);
        if (matcher.find() && matcher.groupCount() >= 2) {
            d.setVersion(matcher.group(2).replaceAll(VPAT, ""));
            d.setModule(matcher.group(1));
            d.setExtension(matcher.group(4));
            d.setExclude(false);
            log.trace("pattern " + resource
              + " matchs a valid deployment resource");
            return d;
        }
        log.trace("pattern " + resource
          + " does not match a valid deployment resource");
        return null;
    }

    public boolean isVersionLessOrEqualTo(Deployment d, String ver) {
        try {
            String cur = d.getVersion().replaceAll("[^\\d^\\.]", "");
            String[] a1 = cur.split("\\.");
            String[] a2 = ver.replaceAll("[^\\d^\\.]", "").split("\\.");
            for (int x = 0; x < Math.min(a1.length, a2.length); x++) {
                int i1 = new Integer(a1[x]).intValue();
                int i2 = new Integer(a2[x]).intValue();
                if (i1 == i2)
                    continue;
                return i1 <= i2;
            }
            return a1.length > a2.length ? false : true;
        } catch (NumberFormatException nf) {
            log.warn("failed to parse version correctly->" + nf);
            throw new RuntimeException("failed to parse version "
              + "correctly->" + nf);
        }
    }

    public boolean isVersionEqualTo(Deployment d, String ver) {
        return d.getVersion().equalsIgnoreCase(ver);
    }

    public void addDeployments() {
        for (Deployment d : batch.values()) {
            log.debug("adding new module " + d.getModule()
              + " at ver " + d.getVersion());
            d.setLastDeploy(new Date());
            plugable.installModule(d);
        }
    }

    public void updateDeployment(Deployment od, Deployment nd) {
        log.debug("updating module " + od.getModule() + " from ver "
          + od.getVersion()  + " to " + nd.getVersion());
        File orig = new File(od.getRepoEntry());
        plugable.reInstallModule(od, nd);
        od.setRepoEntry(nd.getRepoEntry());
        od.setLastDeploy(new Date());
        od.setVersion(nd.getVersion());
        plugable.startModule(od);
        FileUtils.deleteQuietly(orig);
    }

    public void deleteDeployment(Deployment d) {
        log.debug("removing module " + d.getModule()
          + " at ver " + d.getVersion());
        plugable.stopModule(d);
        plugable.uninstallModule(d);
        FileUtils.deleteQuietly(new File(d.getRepoEntry()));
    }

    @SuppressWarnings("unchecked")
    public void mergeDatabase() {
        Map<String, Deployment> tmp = (Map<String, Deployment>)
          ((HashMap<String, Deployment>)batch).clone();
        for (Deployment d : lastBatch.values()) {
            String mod = d.getModule();
            Deployment nd = batch.get(mod);
            if (nd == null) {
                deleteDeployment(d);
                continue;
            }
            batch.remove(mod);
            if (isVersionEqualTo(nd, d.getVersion()))
                continue;
            updateDeployment(d, nd);
        }
        addDeployments();
        lastBatch = tmp;
        for (Deployment sdp : batch.values())
            plugable.startModule(sdp);
        batch = new HashMap<String, Deployment>();
    }

    public boolean acquireResource(String url, String leaf) {
        try {
            File path = new File(repository + "/" + leaf);
            if (path.exists())
                return true;
            log.debug("grabbing file from " + url + " to " + path);
            FileUtils.copyURLToFile(new URL(url), path);
            return true;
        } catch (MalformedURLException ex) {
            log.warn("malformed url constructed: " + url, ex);
            return false;
        } catch (IOException io) {
            log.warn("failed to copy URL of " + url, io);
            return false;
        }
    }

    public void setScanTarget(String scanTarget) {
        this.scanTarget = scanTarget;
    }

    public String getScanTarget() {
        return scanTarget;
    }

    public void setPlugable(Plugable plugable) {
        this.plugable = plugable;
    }

    public Plugable getPlugable() {
        return plugable;
    }

    public void setRecursive(boolean recursive) {
        this.recursive = recursive;
    }

    public void setRepository(String repository) {
        this.repository = repository;
    }

    public String getRepository() {
        return repository;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean getEnabled() {
        return enabled;
    }

}
